package com.yzf.enterprise.framework.config;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NumericNode;
import com.yzf.enterprise.common.utils.JacksonUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * MultiRequestBody解析器
 * 解决的问题：
 * 1、单个字符串等包装类型都要写一个对象才可以用@RequestBody接收；
 * 2、多个对象需要封装到一个对象里才可以用@RequestBody接收。
 * 主要优势：
 * 1、支持通过注解的value指定JSON的key来解析对象。
 * 2、支持通过注解无value，直接根据参数名来解析对象
 * 3、支持基本类型的注入
 * 4、支持GET和其他请求方式注入
 * 5、支持通过注解无value且参数名不匹配JSON串key时，根据属性解析对象。
 * 6、支持多余属性(不解析、不报错)、支持参数“共用”（不指定value时，参数名不为JSON串的key）
 * 7、支持当value和属性名找不到匹配的key时，对象是否匹配所有属性。
 *
 * @author yuyue
 * @version 2020/4/3 14:26
 */
public class MultiRequestBodyArgumentResolver extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private static final String JSON_BODY_ATTRIBUTE = "JSON_REQUEST_BODY";

    public MultiRequestBodyArgumentResolver(List<HttpMessageConverter<?>> converters) {
        super(converters);
    }

    /**
     * 设置支持的方法参数类型
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(MultiRequestBody.class);// 支持带@MultiRequestBody注解的参数
    }

    /**
     * 参数解析
     * 注意：非基本类型返回null会报空指针异常，要通过反射或者JSON工具类创建一个空对象
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
        Assert.notNull(httpServletRequest, "httpServletRequest is null");
        // 有就直接获取
        String jsonBody = (String) webRequest.getAttribute(JSON_BODY_ATTRIBUTE, NativeWebRequest.SCOPE_REQUEST);
        // 没有就从请求中读取
        if (jsonBody == null) {
            try {
                jsonBody = IOUtils.toString(httpServletRequest.getReader());
                webRequest.setAttribute(JSON_BODY_ATTRIBUTE, jsonBody, NativeWebRequest.SCOPE_REQUEST);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        JsonNode jsonNode = JacksonUtil.getMapper().readTree(jsonBody);

        // 根据@MultiRequestBody注解value作为json解析的key
        MultiRequestBody parameterAnnotation = parameter.getParameterAnnotation(MultiRequestBody.class);
        //注解的value是JSON的key
        Assert.notNull(parameterAnnotation, "parameterAnnotation is null");
        String key = parameterAnnotation.value();
        JsonNode jn;
        // 如果@MultiRequestBody注解没有设置value，则取参数名FrameworkServlet作为json解析的key
        if (StringUtils.isNotEmpty(key)) {
            jn = jsonNode.get(key);
            // 如果设置了value但是解析不到，报错
            if (jn == null && parameterAnnotation.required()) {
                throw new IllegalArgumentException(String.format("required param %s is not present", key));
            }
        } else {
            // 注解为设置value则用参数名当做json的key
            key = parameter.getParameterName();
            jn = jsonNode.get(key);
        }

        // 获取的注解后的类型 Long
        Class<?> parameterType = parameter.getParameterType();
        // 通过注解的value或者参数名解析，能拿到value进行解析
        if (jn != null) {
            //基本类型
            if (parameterType.isPrimitive()) {
                return parsePrimitive(parameterType.getName(), jn);
            }
            // 基本类型包装类
            if (isBasicDataTypes(parameterType)) {
                return parseBasicTypeWrapper(parameterType, jn);
                // 字符串类型
            } else if (parameterType == String.class) {
                return jn.textValue();
            }
            // 其他复杂对象
            //JavaType type = JacksonUtil.getMapper().getTypeFactory().constructParametricType();
            //return JacksonUtil.fromJson(jn.toString(), parameter.getGenericParameterType());
            //ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(requestWrapper);
            //return JacksonUtil.fromJson(jn.toString(), TypeFactory.rawClass(parameter.getGenericParameterType()));
            return readWithMessageConverters(new ServletServerHttpRequest(new RequestWrapper(httpServletRequest, jn.toString())), parameter, parameter.getNestedGenericParameterType());
        }

        // 解析不到则将整个json串解析为当前参数类型
        if (isBasicDataTypes(parameterType)) {
            if (parameterAnnotation.required()) {
                throw new IllegalArgumentException(String.format("required param %s is not present", key));
            } else {
                return null;
            }
        }

        // 非基本类型，不允许解析所有字段，必备参数则报错，非必备参数则返回null
        if (!parameterAnnotation.parseAllFields()) {
            // 如果是必传参数抛异常
            if (parameterAnnotation.required()) {
                throw new IllegalArgumentException(String.format("required param %s is not present", key));
            }
            // 否则返回null
            return null;
        }
        // 非基本类型，允许解析，将外层属性解析
        Object result = JacksonUtil.fromJson(jsonNode.toString(), parameterType);

        // 如果非必要参数直接返回，否则如果没有一个属性有值则报错
        if (parameterAnnotation.required()) {
            boolean haveValue = false;
            Field[] declaredFields = parameterType.getDeclaredFields();
            for (Field field : declaredFields) {
                field.setAccessible(true);
                if (field.get(result) != null) {
                    haveValue = true;
                    break;
                }
            }
            if (!haveValue) {
                throw new IllegalArgumentException(String.format("required param %s is not present", key));
            }
        }
        return result;
    }

    /**
     * 基本类型解析
     */
    private Object parsePrimitive(String parameterTypeName, JsonNode jn) {
        final String booleanTypeName = "boolean";
        if (booleanTypeName.equals(parameterTypeName)) {
            return jn.booleanValue();
        }
        final String intTypeName = "int";
        if (intTypeName.equals(parameterTypeName)) {
            return jn.intValue();
        }
        final String charTypeName = "char";
        if (charTypeName.equals(parameterTypeName)) {
            return jn.textValue().charAt(0);
        }
        final String shortTypeName = "short";
        if (shortTypeName.equals(parameterTypeName)) {
            return jn.shortValue();
        }
        final String longTypeName = "long";
        if (longTypeName.equals(parameterTypeName)) {
            return jn.longValue();
        }
        final String floatTypeName = "float";
        if (floatTypeName.equals(parameterTypeName)) {
            return jn.floatValue();
        }
        final String doubleTypeName = "double";
        if (doubleTypeName.equals(parameterTypeName)) {
            return jn.doubleValue();
        }
        //final String byteTypeName = "byte";
        //if (byteTypeName.equals(parameterTypeName)) {
        //    return jn.binaryValue();
        //}
        return null;
    }

    /**
     * 基本类型包装类解析
     */
    private Object parseBasicTypeWrapper(Class<?> parameterType, JsonNode jn) {
        if (Number.class.isAssignableFrom(parameterType) && jn instanceof NumericNode) {
            Number number = jn.numberValue();
            if (parameterType == Integer.class) {
                return number.intValue();
            } else if (parameterType == Short.class) {
                return number.shortValue();
            } else if (parameterType == Long.class) {
                return number.longValue();
            } else if (parameterType == Float.class) {
                return number.floatValue();
            } else if (parameterType == Double.class) {
                return number.doubleValue();
            } else if (parameterType == Byte.class) {
                return number.byteValue();
            }
        } else if (parameterType == Boolean.class) {
            return jn.booleanValue();
        } else if (parameterType == Character.class) {
            return jn.textValue().charAt(0);
        }
        return null;
    }

    /**
     * 判断是否为基本数据类型包装类
     */
    private boolean isBasicDataTypes(Class<?> clazz) {
        Set<Class<?>> classSet = new HashSet<>();
        classSet.add(Integer.class);
        classSet.add(Long.class);
        classSet.add(Short.class);
        classSet.add(Float.class);
        classSet.add(Double.class);
        classSet.add(Boolean.class);
        classSet.add(Byte.class);
        classSet.add(Character.class);
        return classSet.contains(clazz);
    }


}